package com.zoro.barn.business.service.login.business.normalpassword.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.zoro.barn.business.service.login.business.normalpassword.dao.PasswordUserMapper;
import com.zoro.barn.business.service.login.business.normalpassword.entity.PasswordUser;
import com.zoro.barn.business.service.login.business.normalpassword.exceptions.IllegalTokenException;
import com.zoro.barn.business.service.login.business.normalpassword.util.token.TokenPayload;
import com.zoro.barn.business.service.login.business.normalpassword.util.token.TokenPayloadBuilder;
import com.zoro.barn.business.service.login.business.normalpassword.util.token.TokenUtil;
import com.zoro.barn.business.service.login.conf.TokenConfigProperties;
import com.zoro.barn.commons.redis.annotations.CacheDuration;
import com.zoro.barn.commons.redis.annotations.UseCacheDurationConfig;
import com.zoro.barn.commons.tools.encryption.EncryptUtils;
import com.zoro.barn.commons.tools.json.JsonUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import jakarta.annotation.Resource;
import java.time.Duration;
import java.util.Objects;

/**
 * @author zhaoxingwu
 */
@Service
@UseCacheDurationConfig
public class NormalLoginService {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Resource
    private PasswordUserMapper passwordUserMapper;

    private final String secret;
    private final long exp;
    private final long refreshExp;
    private final StringRedisTemplate stringRedisTemplate;

    public NormalLoginService(TokenConfigProperties tokenConfigProperties, StringRedisTemplate stringRedisTemplate) {
        this.secret = tokenConfigProperties.getSecret();
        this.exp = tokenConfigProperties.getExp();
        this.refreshExp = tokenConfigProperties.getRefreshExp();
        this.stringRedisTemplate = stringRedisTemplate;
    }


    /**
     * query user by account and password
     *
     * @param account account
     * @return result
     */
    @CacheDuration(60 * 60 * 1000)
    @Cacheable(cacheNames = "LOGIN_ACCOUNT", key = "#a0+'::'")
    public PasswordUser getByAccount(String account) {
        PasswordUser user = this.passwordUserMapper.getByAccount(account);
        return Objects.requireNonNullElseGet(user, PasswordUser::new);
    }

    /**
     * 生成token
     *
     * @param info payload data
     * @return token
     */
    public String generateToken(PasswordUser info) {

        TokenPayloadBuilder builder = TokenPayload.builder();
        builder.iat().nbf().exp(exp)
                .customized("account", info.getUserAccount())
                .customized("userName", info.getUserName())
                .customized("userId", info.getPid());

        String payload = builder.build();
        return TokenUtil.token(payload, secret);
    }

    /**
     * 验证 refresh token 合法性
     *
     * @param refreshToken refresh token
     * @return user
     */
    public PasswordUser checkRefreshToken(String refreshToken) {
        String userJson = stringRedisTemplate.boundValueOps(redisKey(refreshToken)).get();
        if (StringUtils.isBlank(userJson)) {
            return null;
        } else {
            try {
                return JsonUtil.toJavaType(userJson, PasswordUser.class);
            } catch (Exception e) {
                logger.error("json : " + userJson + " can not parse to user");
                return null;
            }
        }
    }

    /**
     * 生成刷新token
     *
     * @param info user info
     * @return token
     */
    public String generateRefreshToken(PasswordUser info) throws JsonProcessingException {
        if (info == null) {
            return null;
        }
        String token = generateRefreshToken(info.getPid(), info.getUserAccount(), System.currentTimeMillis() + "");
        stringRedisTemplate.opsForValue().set(redisKey(token), JsonUtil.toString(info), Duration.ofMillis(refreshExp));
        return token;
    }

    public String redisKey(String refreshToken) {
        return "REFRESH_TOKEN" + "::" + refreshToken;
    }


    private String generateRefreshToken(String... strings) {
        return EncryptUtils.sha256(concat(secret, strings));
    }

    private String concat(String separator, String[] strings) {
        if (strings == null || strings.length == 0) {
            return null;
        }
        String s = separator;
        if (s == null) {
            s = "";
        }
        return StringUtils.joinWith(s, (Object[]) strings);


    }

    /**
     * 解析token
     *
     * @param token token string
     * @return TokenPayload
     */
    public TokenPayload resolveToken(String token) throws IllegalTokenException {

        if (!TokenUtil.isLegal(token, secret)) {
            throw new IllegalTokenException();
        }

        return TokenUtil.getPayloadObject(token);
    }
}
